package moon.core.classdef;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Scanner;

import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.SimpleDirectedGraph;


import moon.core.Model;
import moon.core.Name;
import moon.core.NamedObject;
import moon.core.entity.Entity;
import moon.core.entity.FunctionDec;
import moon.core.entity.Self;
import moon.core.entity.Super;
import moon.core.genericity.FormalPar;
import moon.core.inheritance.InheritanceClause;
import moon.core.inheritance.Modifier;
import moon.core.inheritance.Rename;
import moon.traverse.Visitor;
import moon.utils.GraphUtil;


/**
 * Definition of a class.
 * 
 * @author <a href="mailto:rmartico@ubu.es">Ra&uacute;l Marticorena</a>
 * @since JavaMoon-2.0
 */
@SuppressWarnings("serial")
public abstract class ClassDef extends NamedObject {
	
	/**
	 * Name space.
	 */
	private NameSpace nameSpace;
	
	/**
	 * Base type.
	 */
	private ClassType baseType;
	
	/**
	 * Formal parameters if the class is generic.
	 */
	private List<FormalPar> formalParameters;
	
	/**
	 * Direct ancestors.
	 */
	private List<InheritanceClause> directAncestors;
	
	/**
	 * Direct descendants.
	 * 
	 */
	private List<ClassDef> directDescendants;	
	
	/**
	 * Attributes.
	 */
	private List<AttDec> attributes;
	
	/**
	 * Methods.
	 */
	private List<MethDec> methods;
	
	/**
	 * Deferred class.
	 * 
	 */
	private boolean deferred;
	
	/**
	 * Constructor.
	 * 
	 * @param name name
	 * @param nameSpace name space
	 */	
	public ClassDef(Name name, 
			NameSpace nameSpace){		
		super(name);
		this.nameSpace = nameSpace;
		if (nameSpace != null){ // if class has nameSpace
			nameSpace.add(this);
		}
		this.formalParameters = new ArrayList<FormalPar>();
		this.directAncestors = new ArrayList<InheritanceClause>();
		this.directDescendants = new ArrayList<ClassDef>();
		this.attributes = new ArrayList<AttDec>();
		this.methods = new ArrayList<MethDec>();
	}
	
	/**
	 * Gets the unique name.
	 * 
	 * @return unique name
	 */
	@Override
	public Name getUniqueName(){
		if (nameSpace!=null){
			return nameSpace.getUniqueName().concat("." + this.getName().toString());
		}
		else {
			return this.getName();
		}
	}
	
	/**
	 * Gets the name space.
	 * 
	 * @return name space
	 * @see #setNameSpace
	 */
	public NameSpace getNameSpace(){
		return this.nameSpace;
	}
	
	/**
	 * Sets the name space.
	 * 
	 * @param nameSpace sets the name space
	 * @see #getNameSpace
	 */
	public void setNameSpace(NameSpace nameSpace){
		this.nameSpace = nameSpace;
	}

	
	/**
	 * Gets if it is available the source code.
	 * 
	 * @return true if the source is available
	 */
	public abstract boolean isSourceAvailable();
	
	/**
	 * Queries if the class is generic.
	 *  
	 * @return true if the class has parameters 
	 */
	public boolean isGeneric(){
		return !this.formalParameters.isEmpty();
	}
	
	/**
	 * Sets the base class type.
	 * 
	 * @param classType base class type
	 * @see #getClassType
	 */
	public void setClassType(ClassType classType){
		this.baseType = classType;
	}
	
	/**
	 * Gets the class type.
	 * 
	 * @return type
	 * @see #setClassType
	 */
	public ClassType getClassType(){ return this.baseType; }
	
	/**
	 * Adds a new inheritance clause, linking to a new ancestor.
	 * 
	 * @param inheritanceClause inheritance clause
	 */
	public void add(InheritanceClause inheritanceClause){		
		this.directAncestors.add(inheritanceClause);	
		inheritanceClause.setClassDef(this);
		ClassType ct = (ClassType) inheritanceClause.getType();		
		ClassDef newAncestor = ct.getClassDef();
		newAncestor.addDescendant(this);
	}
	
	/**
	 * Removes an inheritance clause.
	 * 
	 * @param inheritanceClause inheritance clause
	 */
	public void remove(InheritanceClause inheritanceClause){
		this.directAncestors.remove(inheritanceClause);
		inheritanceClause.setClassDef(null);
		ClassDef newAncestor = ((ClassType) inheritanceClause.getType()).getClassDef();
		newAncestor.removeDescendant(this);
	}
	
	/**
	 * Removes an attribute.
	 * 
	 * @param attDec attribute
	 */
	public void remove(AttDec attDec){
		this.attributes.remove(attDec);
		attDec.setClassDef(null);
	}
	
	/**
	 * Removes a method.
	 * 
	 * @param methDec method
	 */
	public void remove(MethDec methDec){
		this.methods.remove(methDec);
		methDec.setClassDef(null);
	}	
	
	/**
	 * Removes a formal parameter.
	 * 
	 * @param formalPar formal parameter
	 * @since JavaMoon-2.0.2
	 */
	public void remove(FormalPar formalPar){
		this.getFormalPars().remove(formalPar);
		formalPar.setClassDef(null);
	}	
	
	/**
	 * Adds a formal parameter.
	 * 
	 * @param formalPar forma par
	 */
	public void add(FormalPar formalPar){
		if (!this.getFormalPars().contains(formalPar)){
			this.formalParameters.add(formalPar);
			formalPar.setClassDef(this);
		}
	}
	
	/**
	 * Adds a formal parameter in a position.
	 * 
	 * @param formalPar forma parameter
	 * @param index index
	 * @since JavaMoon-2.0.2
	 */
	public void add(FormalPar formalPar, int index){
		if (!this.getFormalPars().contains(formalPar)){
			this.formalParameters.add(index,formalPar);
			formalPar.setClassDef(this);	
		}
	}
	
	/**
	 * Adds an attribute.
	 * 
	 * @param attDec attribute
	 */
	public void add(AttDec attDec){
		if (!this.getAttributes().contains(attDec)){
			this.attributes.add(attDec);
			attDec.setClassDef(this);
		}
		
	}
	
	/**
	 *  Adds a method.
	 *  
	 * @param methDec method
	 */
	public void add(MethDec methDec){
		if (!this.getMethDec().contains(methDec)){
			this.methods.add(methDec);
			methDec.setClassDef(this);
		}
	}
	
	/**
	 * Gets the ancestors.
	 * 
	 * @return ancestors
	 */
	public List<InheritanceClause> getInheritanceClause(){
		return this.directAncestors;
	}
	
	/**
	 * Gets the attributes.
	 * 
	 * @return attributes
	 */
	public List<AttDec> getAttributes(){
		return this.attributes;
	}
	
	/**
	 * Gets the attribute that have this name.
	 * 
	 * @param name name
	 * @return attribute with same name
	 */
	public AttDec getAttDecByName(Name name){		
		for (AttDec ad : this.getAttributes()){
			if (ad.getName().compareTo(name)==0){
				return ad;
			}			
		}
		return null;
	}
	
	/**
	 * Gets the properties (attributes and methods) declared or modified 
	 * with inheritance clause in the class.
	 * 
	 * @return properties as the set of attributes and methods
	 * 
	 */
	public List<Property> getProperties(){
		List<Property> list = new ArrayList<Property>();
		list.addAll(this.getAttributes());
		list.addAll(this.getMethDec());
		return list;
	}
	
	/**
	 * Gets the entities declared in a class (attributes, formal arguments
	 * local declarations, and result entities from functions). 
	 * 
	 * @return entities
	 */
	public List<Entity> getEntities() {
		List<Entity> list = new ArrayList<Entity>();
		list.addAll(this.getAttributes());
		List<MethDec> methods = this.getMethDec();
		for (MethDec md : methods) {
			list.addAll(md.getFormalArgument());
			list.addAll(md.getLocalDecs());
			
			// adding result of function
			if (md.hasReturnType()){
				list.add(((FunctionDec) md).getFunctionResultEntity());
			}
		}
		return list;
	}
	
	/**
	 * Gets the self entity.
	 * 
	 * @return self internal entity
	 */
	public abstract Self getSelfEntity();


	/**
	 * Gets the property by unique name.
	 * 
	 * @param uniqueName unique name
	 * @return property
	 */
	/*
	public Property getProperty(String uniqueName){
		for (Property p : this.getProperties()){
			if (uniqueName.equalsIgnoreCase(p.getUniqueName().toString()));
		}
		return null;
	}*/
	


    /**
     * Gets the methods.
     * 
     * @return methods
     */
	public List<MethDec> getMethDec() {
		return this.methods;
	}
	
	/**
	 * Gets the methods that have this name.
	 * 
	 * @param name name
	 * @return list of methods with same name
	 */
	public List<MethDec> getMethDecByName(Name name){
		List<MethDec> listMethDec = new ArrayList<MethDec>();
		for (MethDec md : this.getMethDec()){
			if (md.getName().compareTo(name)==0){
				listMethDec.add(md);
			}			
		}
		return listMethDec;
	}
	
	
	/**
	 * Gets the method that have the same signature.
	 * 
	 * @param name name
	 * @param list signature
	 * @return method with same signature
	 */
	public MethDec getMethDecBySignature(Name name, List<Type> list){
		boolean found = false;
		MethDec method = null;
	
		for (MethDec md : this.getMethDec()){ 
			
			if (md.getName().compareTo(name)==0){ // equal name
				if (md.getFormalArgument().size() == list.size()){ // equal number of arguments
					int count = 0;
					found = true;
					for (FormalArgument fa : md.getFormalArgument()){		
						
						if (!((fa.getType().getName()).compareTo(list.get(count).getName())==0)){ // equal name of type							
							found = false;
						}
						count++;
					}
					if (found){					
						method = md;
						break;
					}
				}
			}			
		}
		return method;
	}
	
	/**
	 * Gets the method that have the same signature and return type.
	 *
	 * @param name        name
	 * @param list        signature
	 * @param returnType  return type
	 * @return method with same signature and return type
	 */
	/*
	public MethDec getMethDecBySignatureAndReturnType(Name name, List<Type> list, Type returnType){		
		for (MethDec md : this.getMethDec()){ 
			if (md.getName().compareTo(name)==0){ // equal name
				if (md.getFormalArgument().size() == list.size()){ // equal number of arguments
					if (md.hasReturnType()){
						FunctionDec functionDec = (FunctionDec)md;						 
						if (functionDec.getReturnType()==returnType){						
							int count = 0;
							for (FormalArgument fa : md.getFormalArgument()){ 
								if (! ((fa.getType().getName()).compareTo(list.get(count).getName())==0)){ // equal name of type
									break;
								}
								count++;
							}					
							return md;
						}
					}
					else {
						int count = 0;
						for (FormalArgument fa : md.getFormalArgument()){ 
							if (! ((fa.getType().getName()).compareTo(list.get(count).getName())==0)){ // equal name of type
								break;
							}
							count++;
						}					
						return md;
					}
				}
			}			
		}
		return null;
	}*/
	
	/**
	 * Gets the method that have the same signature and return type.
	 *
	 * @param name        name
	 * @param list        signature
	 * @param returnType  return type
	 * @return method with same signature and return type
	 */
	public MethDec getMethDecBySignatureAndReturnType(Name name, List<Type> list, Name returnType){		
		for (MethDec md : this.getMethDec()){ 
			if (md.getName().compareTo(name)==0){ // equal name
				if (md.getFormalArgument().size() == list.size()){ // equal number of arguments
					if (md.hasReturnType()){
						FunctionDec functionDec = (FunctionDec)md;						 
						if (functionDec.getReturnType().getUniqueName().compareTo(returnType)==0){						
							int count = 0;
							for (FormalArgument fa : md.getFormalArgument()){ 
								if (! ((fa.getType().getName()).compareTo(list.get(count).getName())==0)){ // equal name of type
									break;
								}
								count++;
							}					
							return md;
						}
					}
					else {
						int count = 0;
						for (FormalArgument fa : md.getFormalArgument()){ 
							if (! ((fa.getType().getName()).compareTo(list.get(count).getName())==0)){ // equal name of type
								break;
							}
							count++;
						}					
						return md;
					}
				}
			}			
		}
		return null;
	}
	
	/**
	 * Gets the formal parameters.
	 * 
	 * @return formal parameters
	 */
	public List<FormalPar> getFormalPars(){
		return this.formalParameters;
	}
	
	/**
	 * Gets a formal parameter by name.
	 * 
	 * @param name name
	 * @return formal parameter
	 */
	public FormalPar getFormalPar(Name name){
		for (FormalPar fp : this.getFormalPars()){
			if (name.equals(fp.getName())){
				return fp;
			}
		}
		return null;
	}
	
	/**
	 * Gets the index of a formal parameter by name.
	 * 
	 * @param name formal parameter name
	 * @return the index or -1 if it is not found
	 */
	public int getFormalParIndex(Name name){
		int i = 0;
		for (FormalPar fp : this.getFormalPars()){
			if (name.toString().equals(fp.getName().toString())){
				return i;
			}
			i++;
		}
		return -1;
	}
	
	/**
	 * Looks up if exists a formal parameter with this name.
	 * 
	 * @param name name
	 * @return true if exists, false in other case
	 */
	public boolean hasFormalPar(Name name){
		for (FormalPar fp : this.getFormalPars()){
			if (fp.getName().equals(name)){
				return true;
			}
		}
		return false;
	}	
	
	/**
	 * Sets the class as deferred.
	 * 
	 * @param deferred true or false
	 */
	public void setDeferred(boolean deferred){
		this.deferred = deferred;
	}
	
	/**
	 * Gets the current status.
	 * 
	 * @return true if it is deferred or not in other case
	 */
	public boolean isDeferred(){
		return this.deferred;
	}
	
	/**
	 * Returns the list of attributes and methods of the
	 * short flattened class taking into account inheritance rules.
	 *
	 * @return list of properties
	 */
	public abstract List<Property> getShortFlattened();
	
	/**
	 * Returns the list of attributes and methods of the
	 * short flattened class taking into account inheritance rules
	 * but not adding universal properties to interfaces.
	 *
	 * @return list of properties
	 */
	public abstract List<Property> getBasicShortFlattened();

	/**
	 * Gets the code source of the class.
	 * 
	 * @return code
	 */
	public abstract String getCode();
	
	
	/**
	 * Recalcultate new position of an attribute in a class.
	 * 
	 * @param attDec attribute
	 */
	public void format(AttDec attDec){
		attDec.setLine(this.getLineNumbers());		
	}
	
	/**
	 * Recalcultate new position of a method in a class.
	 * 
	 * @param methDec method
	 */
	public void format(MethDec methDec){		
		methDec.setLine(this.getLineNumbers());		
	}
	
	/**
	 * Gets the number of lines in the regenerated source
	 * file.
	 * 
	 * @return code text line number
	 */
	protected long getLineNumbers(){
		String source = this.getCode();	
		Scanner scanner =new Scanner(source);
		int count = 0;
		while(scanner.hasNextLine()){
			scanner.nextLine();
			count++;
		}
		return count;
	}
	
	/**
	 * Renumbers the line number of the elements in the class,
	 * adding 1 to line numbers.
	 * 
	 * @param number new number where the class begins
	 */
	public void reformat(long number){
	
		
		for (InheritanceClause ic: this.getInheritanceClause()){			
			if (ic.getLine()>=number){
				// if ic is below class declaration
				ic.setLine(ic.getLine()+1);
			}
			else{
				// if ic is in the same line of class declaration
				ic.setLine(this.getLine());
			}						
		}
		
		for (MethDec md : this.getMethDec()){
			if (md.getLine()>=number){
				md.setLine(md.getLine()+1);				
			}
		}
		for (AttDec ad : this.getAttributes()){
			if (ad.getLine()>=number){
				ad.setLine(ad.getLine()+1);
			}
		}

	} // reformat
	
	/**
	 * Gets the super entity.
	 * 
	 * @return super internal entity
	 */
	public abstract Super getSuperEntity();
	
	/**
	 * Gets the ancestors.
	 * 
	 * @return ancestors
	 */
	public Collection<ClassDef> getAncestors() {
		SimpleDirectedGraph<ClassDef,DefaultEdge> graph = new SimpleDirectedGraph<ClassDef,DefaultEdge>(DefaultEdge.class);
		//Collection<ClassDef> ancestors = new ArrayList<ClassDef>();
		//ancestors.add(this); // add the own class ?
		//recursiveAddAncestors(this, ancestors);
		graph.addVertex(this); // add the own class
		recursiveAddAncestors(this, graph);
		List<ClassDef> list = GraphUtil.getTopologicalOrder(graph);
		//Collections.reverse(list);
		return list;
		//return ancestors;
	}
	
	/**
	 * Gets the proper ancestors.
	 * 
	 * @return ancestors
	 */
	public Collection<ClassDef> getProperAncestors() {
		Collection<ClassDef> ancestors = new ArrayList<ClassDef>();
		recursiveAddAncestors(this, ancestors);
		return ancestors;
	}
	
	/**
	 * Gets the descendants.
	 * 
	 * @return ancestors
	 */
	public Collection<ClassDef> getDescendants() {
		Collection<ClassDef> descendants = new ArrayList<ClassDef>();
		descendants.add(this); // add the own class ?
		recursiveAddDescendants(this, descendants);
		return descendants;
	}
	
	/**
	 * Gets the proper descendants.
	 * 
	 * @return proper descendants
	 */
	public Collection<ClassDef> getProperDescendants() {
		Collection<ClassDef> descendants = new ArrayList<ClassDef>();
		recursiveAddDescendants(this, descendants);
		return descendants;
	}
	
	/**
	 * Gets direct ancestors or parents. The inheritance path
	 * has a length of 1.
	 * 
	 * @return direct ancestors classes
	 */
	public Collection<ClassDef> getDirectAncestors() {
		Collection<ClassDef> ancestors = new ArrayList<ClassDef>();
		List<InheritanceClause> collection = this.getInheritanceClause();
		for(InheritanceClause ic : collection){
			if(ic.getType() instanceof ClassType){
				ClassType ct2 = (ClassType)ic.getType();
				ancestors.add(ct2.getClassDef());				
			}
		}
		return ancestors;
	}
	
	/**
	 * Gets direct ancestors or parents. The inheritance path
	 * has a length of 1.
	 * 
	 * @return direct ancestors types
	 */
	public Collection<ClassType> getDirectTypeAncestors() {
		Collection<ClassType> ancestors = new ArrayList<ClassType>();
		List<InheritanceClause> collection = this.getInheritanceClause();
		for(InheritanceClause ic : collection){
			if(ic.getType() instanceof ClassType){
				ClassType ct2 = (ClassType)ic.getType();
				ancestors.add(ct2);				
			}
		}
		return ancestors;
	}
	
	/**
	 * Gets direct descendants or children. The inheritance path
	 * has a length of 1. 
	 * 
	 * @return direct descendants
	 */
	public Collection<ClassDef> getDirectDescendants() {
		return this.directDescendants;
	}
	
	/**
	 * Recursive method to collect ancestors.
	 * 
	 * @param cd clase cuyos ancestros se quieren obtener.
	 * @param ancestors lista de ancestros.
	 */
	private void recursiveAddAncestors(ClassDef cd, Collection<ClassDef> ancestors) {

		List<InheritanceClause> collection = cd.getInheritanceClause();
		if (collection.isEmpty()){
			return;
		}
		else {
			for(InheritanceClause ic : collection){
				if(ic.getType() instanceof ClassType){
					ClassType ct2 = (ClassType)ic.getType();
					ancestors.add(ct2.getClassDef());
					// add ancestors recursively
					recursiveAddAncestors(ct2.getClassDef(), ancestors);
				}
			}
		}
		return;
	}
	
	/**
	 * Recursive method to collect ancestors.
	 * 
	 * @param cd clase cuyos ancestros se quieren obtener.
	 * @param ancestors lista de ancestros.
	 */
	private void recursiveAddAncestors(ClassDef cd, SimpleDirectedGraph<ClassDef,DefaultEdge> ancestors) {

		List<InheritanceClause> collection = cd.getInheritanceClause();
		if (collection.isEmpty()){
			return;
		}
		else {
			for(InheritanceClause ic : collection){
				if(ic.getType() instanceof ClassType){
					ClassType ct2 = (ClassType)ic.getType();					
					ancestors.addVertex(ct2.getClassDef());
					ancestors.addEdge(cd, ct2.getClassDef());
					// add ancestors recursively
					recursiveAddAncestors(ct2.getClassDef(), ancestors);
				}
			}
		}
		return;
	}

	/**
	 * Recursive method to collect descendants.
	 * 
	 * @param cd target class
	 * @param descendants descendants
	 */
	private void recursiveAddDescendants(ClassDef cd, Collection<ClassDef> descendants) {

		
		Collection<ClassDef> collection = cd.getDirectDescendants();
		if (collection.isEmpty()){
			return;
		}
		else {
			for(ClassDef cd2 : collection){
				boolean found = false;
				for (ClassDef cd3 : descendants){
					if (cd3.getUniqueName().toString().equals(cd2.getUniqueName().toString())){
						// it is in the list since we shouldn't add
						found = true;
						break;
					}
				}
				if (!found){
					descendants.add(cd2);
					// add ancestors recursively
					recursiveAddDescendants(cd2, descendants);	
				}							
			}
		}
		return;
	}
	
	/**
	 * Adds a new direct descendant (children).
	 * 
	 * @param descendant descendant
	 */
	public void addDescendant(ClassDef descendant){
		if (!this.directDescendants.contains(descendant)){
			this.directDescendants.add(descendant);
		}
	}
	
	/**
	 * Removes a direct descendant (children).
	 * 
	 * @param descendant descendant
	 */
	public void removeDescendant(ClassDef descendant){
		this.directDescendants.remove(descendant);
	}
	
	
	/**
	 * Gets the final name of a property taking into account 
	 * the inheritance path from the ancesto to the current class
	 * (including rename clauses).
	 * 
	 * See [Crespo, 00] page 122.
	 * 
	 * @param property property
	 * @param ancestor ancestor
	 * @return final name taking into account direct inheritance and rename modifiers
	 */
	private Name getFinalName(Property property, ClassDef ancestor){
		if (!this.isDirectDescendantOf(ancestor)){
			return null;
		}
		else{
			List<InheritanceClause> listIC = this.getInheritanceClause();
			for (InheritanceClause ic : listIC){
				if (ic.getType().getClassDef().getUniqueName().toString().equals(ancestor.getUniqueName().toString())){
					List<Modifier> modifiers = ic.getModifiers();
					for (Modifier mod : modifiers){
						if (mod instanceof Rename && mod.getProperty().getUniqueName().toString().equals(property.getUniqueName().toString())){
							return ((Rename)mod).getName();
							// there is an inheritance clause from this to ancestor, with rename n as nn
						}
					}	
				}				
			}			
		}		
		return property.getName();
	}
	
	
	/**
	 * Gets the final names of a property following the inheritance paths.
	 * See [Crespo, 00] page 122.
	 * 
	 * @param property property
	 * @param ancestor ancestor
	 * @return list of names
	 */
	public List<Name> getRename(Property property, ClassDef ancestor){
		List<Name> list = new ArrayList<Name>();
		
		if (!this.isDescendantOf(ancestor)){
			return list;
		}
		
		if (this.isDirectDescendantOf(ancestor)){
			list.add(this.getFinalName(property, ancestor));
			return list;
		}
		else{
			Collection<ClassDef> parents = this.getDirectAncestors();
			for(ClassDef cd : parents){
				list.addAll(cd.getRename(property, ancestor));
			}
			return list;
		}
		
		
	}
	
	/**
	 * Checks if a class is descendant of other.
	 * 
	 * @param classDef ancestor
	 * @return true if this class inherits from ancestor, false in other case
	 */
	public boolean isDescendantOf(ClassDef classDef){
		Collection<ClassDef> list = classDef.getDescendants();
		for (ClassDef cd : list){
			if (cd.getUniqueName().toString().equals(this.getUniqueName().toString())) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Checks if a class is direct descendant of other.
	 * 
	 * @param classDef direct ancestor
	 * @return true if this class inherits from the direct ancestor, false in other case
	 */
	public boolean isDirectDescendantOf(ClassDef classDef){
		Collection<ClassDef> list = classDef.getDirectDescendants();
		for (ClassDef cd : list){
			if (cd.getUniqueName().toString().equals(this.getUniqueName().toString())) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Checks if a class is ancestor of other.
	 * 
	 * @param descendant descendant
	 * @return true if this class has the direct descendant, false in other case
	 */
	public boolean isAncestorOf(ClassDef descendant){
		Collection<ClassDef> list = descendant.getAncestors();
		for (ClassDef cd : list){
			if (cd.getUniqueName().toString().equals(this.getUniqueName().toString())) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Checks if a class is a direct ancestor of other.
	 * 
	 * @param descendant descendant
	 * @return true if this class has the direct descendant, false in other case
	 */
	public boolean isDirectAncestorOf(ClassDef descendant){
		Collection<ClassDef> list = descendant.getDirectAncestors();
		for (ClassDef cd : list){
			if (cd.getUniqueName().toString().equals(this.getUniqueName().toString())) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Gets the direct providers of this class.
	 * 
	 * @return direct providers
	 */
	public List<ClassDef> getDirectProvider(){
		HashMap<String, ClassDef> hash = new HashMap<String, ClassDef>();
		// #1 dependencies from attributes
		List<AttDec> attributes = this.getAttributes();
		for (AttDec attDec : attributes){			
			addInMapType(attDec.getType(), hash);			
		}
		// #2 dependencies from methods
		List<MethDec> methods = this.getMethDec();
		for (MethDec methDec : methods){
			// # 2.1 signature
			// # 2.2 formal arguments
			List<FormalArgument> formalArguments = methDec.getFormalArgument();
			for (FormalArgument fa : formalArguments){
				addInMapType(fa.getType(), hash);
			}
			// # 2.3 return type if exists
			if (methDec.hasReturnType()){
				addInMapType(((FunctionDec) methDec).getReturnType(), hash);
			}
			// # 2.4 local decs
			List<LocalDec> locals = methDec.getLocalDecs();
			for (LocalDec localDec : locals){
				addInMapType(localDec.getType(), hash);
			}			
		}
		return new ArrayList<ClassDef>(hash.values());
	}
	
	/**
	 * Adds a type to a hash table.
	 * 
	 * @param type type
	 * @param hash hashtable
	 */
	private void addInMapType(Type type, HashMap<String,ClassDef> hash){
		// FIXME some att from binary load don't have type?
		if (type != null && type.getClassDef()!=null && type instanceof ClassType &&
				!hash.containsKey(type.getClassDef().getUniqueName().toString())){
			hash.put(type.getClassDef().getUniqueName().toString(),type.getClassDef());
		}
		// Recursively add types from substitutions...
		if (type instanceof ClassType && ((ClassType) type).isParametricType()){
			List<Type> list = ((ClassType) type).getRealParameters();
			for (Type type2 : list){
				this.addInMapType(type2, hash);
			}
		}
	}
	
	/**
	 * Gets the direc providers of this class.
	 * 
	 * @return direct providers
	 * @see #getDirectClient
	 */
	public List<ClassDef> getDirectClient(){
		HashMap<String, ClassDef> hash = new HashMap<String, ClassDef>();
		Collection<ClassDef> collection = this.getModel().getClassDef();
		for (ClassDef cd : collection){
			List<ClassDef> clients = cd.getDirectProvider();
			for (ClassDef client : clients){
				if (client.getUniqueName().toString().equals(this.getUniqueName().toString())){
					if (!hash.containsKey(cd.getUniqueName().toString())){
						hash.put(cd.getUniqueName().toString(), cd);
					}
					break;
				}
				
			}
		}
		return new ArrayList<ClassDef>(hash.values());
		
	}
	
	/**
	 * Gets the model that contains this class.
	 * 
	 * @return model
	 */
	public abstract Model getModel();
	
	
	/**
	 * Gets the clients.
	 * 
	 * @return ancestors
	 */
	public List<ClassDef> getClients() {
		HashMap<String,ClassDef> clients = new HashMap<String,ClassDef>();
		recursiveAddClients(this, clients);
		return new ArrayList<ClassDef>(clients.values());
	}
	
	/**
	 * Recursive method to collect clients.
	 * 
	 * @param cd class
	 * @param hash client classes
	 */
	private void recursiveAddClients(ClassDef cd, HashMap<String,ClassDef> hash) {
		List<ClassDef> collection = cd.getDirectClient();
		if (collection.isEmpty()){
			return;
		}
		else {
			for(ClassDef cd2 : collection){
				if (!hash.containsKey(cd2.getUniqueName().toString()) &&
						cd2.getClassType()!=null){
					hash.put(cd2.getUniqueName().toString(), cd2);
					// add clients recursively
					recursiveAddClients(cd2, hash);
				}								
			}
		}
		return;
	}
	
	/**
	 * Gets the clients.
	 * 
	 * @return ancestors
	 */
	public List<ClassDef> getProviders() {
		HashMap<String,ClassDef> providers = new HashMap<String,ClassDef>();
		recursiveAddProviders(this, providers);
		return new ArrayList<ClassDef>(providers.values());
	}
	
	/**
	 * Recursive method to collect clients.
	 * 
	 * @param cd class
	 * @param hash client classes
	 */
	private void recursiveAddProviders(ClassDef cd, HashMap<String,ClassDef> hash) {
		List<ClassDef> collection = cd.getDirectProvider();
		if (collection.isEmpty()){
			return;
		}
		else {
			for(ClassDef cd2 : collection){
				if (!hash.containsKey(cd2.getUniqueName().toString()) &&
						cd2.getClassType()!=null){
					hash.put(cd2.getUniqueName().toString(), cd2);
					// add providers recursively
					recursiveAddProviders(cd2, hash);
				}								
			}
		}
		return;
	}
	
	/**
	 * Gets the conditioner classes.
	 * 
	 * @return classes that condition this class
	 */
	public List<ClassDef> getConditioner(){
		// Anc(C) Union Prov(C)
		Collection<ClassDef> ancestors = this.getAncestors();
		List<ClassDef> providers = this.getProviders();
		// union
		return union(new ArrayList<ClassDef>(ancestors),providers);				
	}
	
	/**
	 * Gets the dependent classes.
	 * 
	 * @return classes that depend on this class
	 */
	public List<ClassDef> getDependent(){
		// Desc(C) Union Client(C)
		Collection<ClassDef> descendants = this.getDescendants();
		List<ClassDef> clients = this.getClients();
		// union
		return union(new ArrayList<ClassDef>(descendants),clients);				
	}
	
	/**
	 * Gets the conditioner classes.
	 * 
	 * @return classes that condition this class
	 */
	public List<ClassDef> getDirectConditioner(){
		// DAnc(C) Union DProv(C)
		Collection<ClassDef> ancestors = this.getDirectAncestors();
		List<ClassDef> providers = this.getDirectProvider();
		// union
		return union(new ArrayList<ClassDef>(ancestors),providers);		
	}
	
	/**
	 * Union of two set of classes.
	 * 
	 * @param list1 list of classes
	 * @param list2 list of classes
	 * @return union of lists
	 */
	private List<ClassDef> union(List<ClassDef> list1, List<ClassDef> list2){
		HashMap<String, ClassDef> hash =new HashMap<String, ClassDef>();
		for (ClassDef cd : list1){
			if (!hash.containsKey(cd.getUniqueName().toString())){
				hash.put(cd.getUniqueName().toString(), cd);
			}
		}
		for (ClassDef cd : list2){
			if (!hash.containsKey(cd.getUniqueName().toString())){
				hash.put(cd.getUniqueName().toString(), cd);
			}
		}
		return new ArrayList<ClassDef>(hash.values());
	}
	
	/**
	 * Gets the dependent classes.
	 * 
	 * @return classes that depend on this class
	 */
	public List<ClassDef> getDirectDependent(){
		// DDesc(C) Union DClient(C)
		Collection<ClassDef> descendants = this.getDirectDescendants();
		List<ClassDef> clients = this.getDirectClient();
		// union
		return union(new ArrayList<ClassDef>(descendants),clients);				
	}
	
	/**
	 * Copy the information.
	 * 
	 * @param classDef source class
	 */
	public void copy(ClassDef classDef){
		this.setLine(classDef.getLine());
		this.setColumn(classDef.getColumn());
		this.setName(classDef.getName());
		this.attributes = classDef.attributes;
		this.baseType = classDef.baseType;
		this.deferred = classDef.deferred;
		this.directAncestors = classDef.directAncestors;
		this.directDescendants = classDef.directDescendants;
		this.formalParameters = classDef.formalParameters;
		this.methods = classDef.methods;
		this.nameSpace = classDef.nameSpace;
	}
	
	/**
     * Accepts the visit. Method from Pattern Design Visitor.
     * 
     * @param visitor visitor
     */
    @Override
    public void accept(Visitor visitor){
    	// Hook method
    	visitor.visit(this);
    }
    
    /**
     * Gets the intrinsic properties of the class.
     * 
     * @return intrinsic properties
     */
    public List<Property> getIntrinsics() {
    	List<Property> result = new ArrayList<Property>();
    	for (Property property : this.getProperties()){
    		if (property.isIntrinsic()){
    			result.add(property);
    		}
    	}
    	return result; 
    }
    
    /**
     * Gets the heir properties of the class.
     * 
     * @return heir properties
     */
    public List<Property> getHeirs(){
    	
    	List<Property> result = new ArrayList<Property>();
    	result = this.getShortFlattened();
    	result.removeAll(this.getIntrinsics());
    	return result; 
    }
    
    
	/**
	 * Adds the inheritance modifiers to the methods of this class.
	 * 
	 */
	public abstract void addInheritanceModifiers();
	
	
	/**
	 * Checks if the class contains the set of properties.
	 */
	public boolean containsSignature(List<Property> list) {
		boolean found = true;
		List<Property> propertiesInClass = this.getBasicShortFlattened();
		// if it is an interface ?
		int foundInList = 0;
		for (Property listProperty : list) {			
			for (Property prop : propertiesInClass) {
				System.out.println(listProperty.getUniqueName().toString() + " is equiv to " +
					prop.getUniqueName().toString() + "?");
				if (listProperty.isEquiv(prop)){
					System.out.println("Yes is equivalent.");
					foundInList++;
					break;
				}
			}			
		}
		System.out.println("Properties found: " + foundInList + " List size:" + list.size());
		if (foundInList != list.size()) {
			return false;
		}
		return true;
	}
} // ClassDef

